﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using Boo.Lang;
using MbUnit.Framework;
using Mite.Configuration;
using Mite.MsSql;
using Moq;
namespace Mite.Core.Tests.Unit
{
	[TestFixture]
	public class MigrationRunnerTestFixture
	{
		[Test]
		public void Migrate_to_equal_version_does_nothing()
		{
			Mock<IAdapter> adapterMock = GetAdapterMock(1);

			IAdapterFactory factory = GetAdapterFactory(adapterMock.Object);

			MigrationRunner runner = GetMigrationRunner(factory);
			runner.Migrate(GetEnvironment(), 1);
		}

		[Test]
		public void Migrate_to_greater_version_does_not_run_migration_with_current_schema_version()
		{
			const int schemaVersion = 1;
			const int targetVersion = 2;

			Mock<IAdapter> adapterMock = GetAdapterMock(schemaVersion);
			IAdapterFactory factory = GetAdapterFactory(adapterMock.Object);

			var migrationMock = new Mock<MigrationBase>(GetEnvironment(), adapterMock.Object);
			migrationMock.ExpectGet(x => x.Version).Returns(schemaVersion);
			migrationMock.Expect(x => x.Run(It.IsAny<MigrationDirection>())).Callback(() => Assert.Fail("This migration should not have run."));

			MigrationRunner runner = GetMigrationRunner(factory);
			runner.Migrate(GetEnvironment(), targetVersion, migrationMock.Object);
		}

		[Test]
		public void Migrate_to_greater_version_runs_migration_with_higher_version_number_than_current()
		{
			Mock<IAdapter> adapterMock = GetAdapterMock(1);
			IAdapterFactory factory = GetAdapterFactory(adapterMock.Object);

			var migrationMock = new Mock<MigrationBase>(GetEnvironment(), adapterMock.Object);
			migrationMock.ExpectGet(x => x.Version).Returns(2);
			migrationMock.Expect(x => x.Run(It.Is<MigrationDirection>(arg => arg == MigrationDirection.Up))).Verifiable();

			MigrationRunner runner = GetMigrationRunner(factory);
			runner.Migrate(GetEnvironment(), 2, migrationMock.Object);

			migrationMock.Verify();
		}

		[Test]
		public void Migrate_to_greater_version_does_not_run_migration_with_lower_version_than_current()
		{
			const int schemaVersion = 2;
			const int targetVersion = 3;

			Mock<IAdapter> adapterMock = GetAdapterMock(schemaVersion);

			IAdapterFactory factory = GetAdapterFactory(adapterMock.Object);

			var migrationMock = new Mock<MigrationBase>(GetEnvironment(), adapterMock.Object);
			migrationMock.ExpectGet(x => x.Version).Returns(1);
			migrationMock.Expect(x => x.Run(It.Is<MigrationDirection>(arg => arg == MigrationDirection.Up))).Callback(
				() => Assert.Fail("Run should not be called"));

			MigrationRunner runner = GetMigrationRunner(factory);
			runner.Migrate(GetEnvironment(), targetVersion, migrationMock.Object);
		}

		[Test]
		public void Migrate_to_lower_version_runs_migration_with_schema_version()
		{
			const int schemaVersion = 2;
			const int targetVersion = 1;

			Mock<IAdapter> adapterMock = GetAdapterMock(schemaVersion);

			IAdapterFactory factory = GetAdapterFactory(adapterMock.Object);

			var migrationMock = new Mock<MigrationBase>(GetEnvironment(), adapterMock.Object);
			migrationMock.ExpectGet(x => x.Version).Returns(schemaVersion);
			migrationMock.Expect(x => x.Run(It.Is<MigrationDirection>(arg => arg == MigrationDirection.Down))).Verifiable();

			MigrationRunner runner = GetMigrationRunner(factory);
			runner.Migrate(GetEnvironment(), targetVersion, migrationMock.Object);

			migrationMock.Verify();
		}

		[Test]
		public void Migrate_to_lower_version_runs_migration_with_version_between_schema_and_target()
		{
			const int schemaVersion = 3;
			const int targetVersion = 1;

			Mock<IAdapter> adapterMock = GetAdapterMock(schemaVersion);

			IAdapterFactory factory = GetAdapterFactory(adapterMock.Object);

			var migrationMock = new Mock<MigrationBase>(GetEnvironment(), adapterMock.Object);
			migrationMock.ExpectGet(x => x.Version).Returns(2);
			migrationMock.Expect(x => x.Run(It.Is<MigrationDirection>(arg => arg == MigrationDirection.Down))).Verifiable();

			MigrationRunner runner = GetMigrationRunner(factory);
			runner.Migrate(GetEnvironment(), targetVersion, migrationMock.Object);

			migrationMock.Verify();
		}

		[Test]
		public void Migrate_up_runs_migrations_in_order()
		{
			const int schemaVersion = 0;
			const int targetVersion = 2;

			bool oneHasRun = false;
			bool twoHasRun = false;

			Mock<IAdapter> adapterMock = GetAdapterMock(schemaVersion);
			IAdapterFactory factory = GetAdapterFactory(adapterMock.Object);

			var migrationOneMock = GetMigrationMock(GetEnvironment(), adapterMock.Object, 1, MigrationDirection.Up, () =>
			{
				Assert.IsFalse(twoHasRun);
				oneHasRun = true;
			});
			var migrationTwoMock = GetMigrationMock(GetEnvironment(), adapterMock.Object, 2, MigrationDirection.Up,
				() =>
				{
					Assert.IsTrue(oneHasRun);
					twoHasRun = true;
				});

			MigrationRunner runner = GetMigrationRunner(factory);
			runner.Migrate(GetEnvironment(), targetVersion, migrationOneMock.Object, migrationTwoMock.Object);

			migrationOneMock.Verify();
			migrationTwoMock.Verify();
		}

		[Test]
		public void Migrate_down_runs_migrations_in_descending_order()
		{
			const int schemaVersion = 2;
			const int targetVersion = 0;

			bool oneHasRun = false;
			bool twoHasRun = false;

			Mock<IAdapter> adapterMock = GetAdapterMock(schemaVersion);
			IAdapterFactory factory = GetAdapterFactory(adapterMock.Object);

			var migrationTwoMock = GetMigrationMock(GetEnvironment(), adapterMock.Object, 2, MigrationDirection.Down, () =>
			{
				Assert.IsFalse(oneHasRun);
				twoHasRun = true;
			});
			var migrationOneMock = GetMigrationMock(GetEnvironment(), adapterMock.Object, 1, MigrationDirection.Down,
				() =>
				{
					Assert.IsTrue(twoHasRun);
					oneHasRun = true;
				});

			MigrationRunner runner = GetMigrationRunner(factory);
			runner.Migrate(GetEnvironment(), targetVersion, migrationOneMock.Object, migrationTwoMock.Object);

			migrationOneMock.Verify();
			migrationTwoMock.Verify();
		}

		private Mock<MigrationBase> GetMigrationMock(MiteEnvironment environment, IAdapter adapter, uint version, MigrationDirection direction)
		{
			return GetMigrationMock(environment, adapter, version, direction, () => { });
		}

		private Mock<MigrationBase> GetMigrationMock(MiteEnvironment environment, IAdapter adapter, uint version, MigrationDirection direction, Action callback)
		{
			var migrationMock = new Mock<MigrationBase>(GetEnvironment(), adapter);
			migrationMock.ExpectGet(x => x.Version).Returns(version);
			migrationMock.Expect(x => x.Run(It.Is<MigrationDirection>(arg => arg == direction))).Callback(callback).Verifiable();

			return migrationMock;
		}

		private MiteEnvironment GetEnvironment()
		{
			return new MiteEnvironment("Test", typeof(MsSqlAdapter), "localhost", "doesnotmatter", "", "", new Hash());
		}

		private Mock<IDbConnection> GetConnectionMock()
		{
			Mock<IDbConnection> connectionMock = new Mock<IDbConnection>();

			Mock<IDbTransaction> transactionMock = new Mock<IDbTransaction>();
			transactionMock.ExpectGet(x => x.Connection).Returns(connectionMock.Object);

			Mock<IDbCommand> cmdMock = new Mock<IDbCommand>();
			cmdMock.ExpectGet(x => x.Connection).Returns(connectionMock.Object);
			cmdMock.Stub(x => x.CommandText);

			connectionMock.Expect(x => x.BeginTransaction()).Returns(transactionMock.Object);
			connectionMock.Expect(x => x.CreateCommand()).Returns(cmdMock.Object);

			return connectionMock;
		}

		private Mock<IAdapter> GetAdapterMock(int schemaVersion)
		{
			var adapterMock = new Mock<IAdapter>();
			adapterMock.Expect(x => x.CreateConnection(It.IsAny<string>(),
													   It.IsAny<string>(),
													   It.IsAny<string>(),
													   It.IsAny<string>(),
													   It.IsAny<Hash>())).Returns(GetConnectionMock().Object);

			adapterMock.Expect(x => x.GetSchemaVersion(It.IsAny<IDbConnection>(), It.IsAny<Database>())).Returns(schemaVersion);

			return adapterMock;
		}

		private IAdapterFactory GetAdapterFactory(IAdapter adapter)
		{
			Mock<IAdapterFactory> factoryMock = new Mock<IAdapterFactory>();
			factoryMock.Expect(x => x.Create(It.IsAny<MiteEnvironment>()))
				.Returns(adapter);

			return factoryMock.Object;
		}

		private MigrationRunner GetMigrationRunner(IAdapterFactory factory)
		{
			return new MigrationRunner(factory);
		}
	}
}
